package testutil

import (
	"errors"
	"fmt"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
)

// ExpectEQ 判断对象相等或者数值类型的值相等
/* *****************************************
* 如果实际值val1与期望值val2 不相等，格式化输出错误信息,additions 是附加信息
*  如下例子:
Fail to execute usrcenter.test_parsePhoneNumber()  in user_test.go:111
	 Actual value: 135209489999
	 Expect value: 13520948999
 	 Addction Info: 0086-135-2094-8999
******************************************************************************/
func ExpectEQ(val1 interface{}, val2 interface{}, additions ...interface{}) {
	if val1 == val2 {
		return
	}
	printErrorInfo(val1, val2, additions...)
}

// ExpectNE 判断对象或者数值类型的值小于相等
func ExpectNE(val1 interface{}, val2 interface{}, additions ...interface{}) {
	if val1 != val2 {
		return
	}
	s := fmt.Sprintf("!%v", val2)
	printErrorInfo(val1, s, additions...)
}

// ExpectNull 空值判断，只支持map，slice和channel等nil空值类型
func ExpectNull(val1 interface{}, additions ...interface{}) {
	if val1 == nil {
		return
	}
	fmt.Printf("val1 is %v", val1)
	printErrorInfo(val1, nil, additions...)
}

// ExpectNotNull 非空判断，只支持map，slice和channel等nil空值类型
func ExpectNotNull(val1 interface{}, additions ...interface{}) {
	if val1 != nil {
		return
	}
	printErrorInfo(val1, "not nil", additions...)
}

// ExpectTrue boolean类型真值判断
func ExpectTrue(condition bool, additions ...interface{}) {
	if condition == true {
		return
	}
	printErrorInfo(condition, true, additions...)
}

// ExpectFalse boolean类型假值判断
func ExpectFalse(condition bool, additions ...interface{}) {
	if condition == false {
		return
	}
	printErrorInfo(condition, false, additions...)

}

// ExpectStructEQ 比较结构体可导出成员是否相等
func ExpectStructEQ(strct1 interface{}, strct2 interface{}) {
	value1 := reflect.ValueOf(strct1)
	value2 := reflect.ValueOf(strct2)
	for i := 0; i < value1.NumField(); i++ {
		val1, err1 := TypeConversion(fmt.Sprintf("%v", value1.Field(i)), value1.Field(i).Type().Name()) //类型转换
		val2, err2 := TypeConversion(fmt.Sprintf("%v", value2.Field(i)), value2.Field(i).Type().Name()) //类型转换
		if err1 != nil || err2 != nil {
			fmt.Println(err1.Error())
			fmt.Println(err2.Error())
			continue
		}
		ExpectEQ(val1, val2, fmt.Sprintf("field : %s wanted : %v, exact : %v", value1.Type().Field(i).Name, val1, val2))
	}
}

// CallerName 返回调用栈的函数名,文件名，行号
//skip 指示是返回第几层栈的信息
func CallerName(skip int) (funName, file string, line int, ok bool) {
	var pc uintptr
	if pc, file, line, ok = runtime.Caller(skip + 1); !ok {
		return
	}
	funName = runtime.FuncForPC(pc).Name()
	file = trimPath(file)
	funName = trimPath(funName) + "()"
	return
}

// CallerStack 打印一定层次的调用堆栈信息
func CallerStack(skip int) {
	for i := 0; i < skip; i++ {
		name, v, d, bl := CallerName(i)
		fmt.Printf("function : %s ,file : %s, line : %d, isOK : %v\n", name, v, d, bl)
	}
}

// trimPath 如：输入: c:\go\src\test.go  输出为 test.go
// 把 \usr\local\test.go   输出为  test.go
func trimPath(absoluteFileName string) string {
	sep := "/"
	if !strings.Contains(absoluteFileName, sep) {
		sep = "\\"
	}
	s := strings.Split(absoluteFileName, sep)
	return s[len(s)-1]
}

// trimPath1 重写了trimPath，不增加内存分配实现，仅仅是对trimPath的优化。如：输入: c:\go\src\test.go  输出为 test.go
// 把 \usr\local\test.go   输出为  test.go
func trimPath1(absoluteFileName string) string {
	sep := "/"
	if !strings.Contains(absoluteFileName, sep) {
		sep = "\\"
	}
	i := strings.LastIndex(absoluteFileName, sep)
	if i == -1 {
		return absoluteFileName
	}
	return absoluteFileName[i:]
}

// printErrorInfo 格式化打印附加信息
func printErrorInfo(val1 interface{}, val2 interface{}, additions ...interface{}) {
	funName, file, line, _ := CallerName(2)
	s := fmt.Sprintf("Fail to execute %s  in %v:%v", funName, file, line)
	s = fmt.Sprintf("%s \n\t Actual value: %v\n\t Expect value: %v \n",
		s,
		val1,
		val2)

	for _, addition := range additions {
		s = fmt.Sprintf("%s \t Addction Info: %v \n", s, addition)
	}
	//log.Fatalf("%s", s)
	fmt.Printf("%s", s)

}

// TypeConversion 类型转换
func TypeConversion(value string, ntype string) (interface{}, error) {
	if ntype == "string" {
		return value, nil
	} else if ntype == "time.Time" {
		t, err := time.ParseInLocation("2006-01-02 15:04:05", value, time.Local)
		return t, err
	} else if ntype == "int" {
		i, err := strconv.Atoi(value)
		return i, err
	} else if ntype == "int8" {
		i, err := strconv.ParseInt(value, 10, 8)
		return int8(i), err
	} else if ntype == "int32" {
		i, err := strconv.ParseInt(value, 10, 32)
		return int64(i), err
	} else if ntype == "int64" {
		i, err := strconv.ParseInt(value, 10, 64)
		return i, err
	} else if ntype == "uint8" {
		i, err := strconv.ParseUint(value, 10, 8)
		return int8(i), err
	} else if ntype == "uint32" {
		i, err := strconv.ParseUint(value, 10, 32)
		return uint32(i), err
	} else if ntype == "uint64" {
		i, err := strconv.ParseUint(value, 10, 64)
		return uint64(i), err
	} else if ntype == "float32" {
		i, err := strconv.ParseFloat(value, 64)
		return float32(i), err
	} else if ntype == "float64" {
		i, err := strconv.ParseFloat(value, 64)
		return i, err
	} else if ntype == "bool" {
		i, err := strconv.ParseBool(value)
		return i, err
	}
	//else if .......增加其他一些类型的转换

	//	return reflect.Value{}, nil
	return value, errors.New("未知的类型：" + ntype)
}
